package com.hmdp.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static java.lang.Thread.sleep;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryById(Long id) {
        //解决缓存穿透
        //Shop shop = queryByIdWithRedisPenetration(id);

        //解决缓存击穿 -  互斥锁
        Shop shop = queryByIdWithRedisPuncture(id);
        return Result.ok(shop);
    }
    //缓存穿透解决方法
    private Shop queryByIdWithRedisPenetration(Long id) {
        //1.redis中查询商铺信息
        String shopJson = stringRedisTemplate.opsForValue().get("cache:shop:" + id);
        //2.存在则返回商铺信息
        if(StrUtil.isNotBlank(shopJson)){
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        //判断命中是否为空值
        if(shopJson != null){
            return null;
        }
        //3.不存在则查询数据库
        Shop shop1 = getById(id);

        //4.数据库中不存在则返回错误
        if(shop1==null){
            //保存空数据到redis中 以解决缓存穿透问题
            stringRedisTemplate.opsForValue().set("cache:shop:" + id, "", 2L, java.util.concurrent.TimeUnit.MINUTES);
            return null;
        }
        //5.数据库中存在则返回商铺信息并保存到redis中
        stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop1), 30L, java.util.concurrent.TimeUnit.MINUTES);
        return shop1;
    }

    //缓存击穿解决方法 - 互斥锁
    private Shop queryByIdWithRedisPuncture(Long id) {
        //1.redis中查询商铺信息
        String shopJson = stringRedisTemplate.opsForValue().get("cache:shop:" + id);
        //2.存在则返回商铺信息
        if(StrUtil.isNotBlank(shopJson)){
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        //判断命中是否为空值
        if(shopJson != null){
            return null;
        }
        //尝试重建缓存
        Shop shop1 = null;
        try {
            //3.1尝试获取互斥锁
            boolean lockflag = tryLock("lock:shop:" + id);
            //3.2获取锁失败 休眠并重试
            if (!lockflag){
                //休眠并重试
                sleep(50);
                return queryByIdWithRedisPuncture(id);
            }
            //3.3获取锁成功
            //4.不存在则查询数据库
            shop1 = getById(id);
            //模拟复杂查询
            sleep(200);
            //5.数据库中不存在则返回错误
            if(shop1==null){
                //保存空数据到redis中 以解决缓存穿透问题
                stringRedisTemplate.opsForValue().set("cache:shop:" + id, "", 2L, java.util.concurrent.TimeUnit.MINUTES);
                return null;
            }
            //6.数据库中存在则返回商铺信息并保存到redis中
            stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop1), 30L, java.util.concurrent.TimeUnit.MINUTES);

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //释放互斥锁
            unLock("lock:shop:" + id);
        }

        return shop1;
    }

    //互斥锁的获取
    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10L, java.util.concurrent.TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }
    //互斥锁的释放
    private void unLock(String key) {
        stringRedisTemplate.delete(key);
    }

    @Override
    @Transactional
    public Result update(Shop shop) {
        if (shop == null){
            return Result.fail("店铺信息不能为空");
        }
        //更新数据库
        updateById(shop);
        //删除缓存
        stringRedisTemplate.delete("cache:shop:" + shop.getId());
        return null;
    }
}
